﻿//*** Guidance generated code ***//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Wordprocessing;

using ProjectBase.Core;

namespace ProjectBase.Tools.Export.Word
{
    public class DocumentParser
    {
        public virtual IEnumerable<DocPart> Execute(Body body)
        {
            var parts = new List<DocPart>();

            var bookmarks = body.Descendants<BookmarkStart>().ToList();
            var ends = body.Descendants<BookmarkEnd>().ToList();

            FixTheBookmarkEndPosition(body, ends);

            while (FixTheBookmarkStartPosition(bookmarks, ends)) { }
            while (FixTheBookmarkEndSiblings(bookmarks, ends)) { }

            bookmarks = body.Descendants<BookmarkStart>().ToList();

            ProcessBookmarks(body, bookmarks, parts);

            return parts;
        }
        protected virtual bool FixTheBookmarkStartPosition(IEnumerable<BookmarkStart> bookmarks, IList<BookmarkEnd> ends)
        {
            var wasChange = false;

            foreach (var start in bookmarks.Where(b => b.NextSibling() is BookmarkStart).ToList())
            {
                var sibling = start.NextSibling() as BookmarkStart;

                var startBookmark = start; // closure
                var first = ends.IndexOf(ends.First(e => e.Id.Value.Equals(startBookmark.Id.Value)));
                var next = ends.IndexOf(ends.First(e => e.Id.Value.Equals(sibling.Id.Value)));

                if (first >= next)
                {
                    continue;
                }

                sibling.Remove();
                start.InsertBeforeSelf(sibling);
                wasChange = true;
            }

            return wasChange;
        }

        protected virtual bool FixTheBookmarkEndSiblings(IList<BookmarkStart> bookmarks, IEnumerable<BookmarkEnd> ends)
        {
            var wasChange = false;

            var list = ends.Where(b => b.NextSibling() is BookmarkEnd).ToList();

            foreach (var end in list)
            {
                var sibling = end.NextSibling() as BookmarkEnd;

                var endBookmark = end;
                var endStart = bookmarks.First(b => b.Id.Value.Equals(endBookmark.Id.Value));
                var siblingStart = bookmarks.First(b => b.Id.Value.Equals(sibling.Id.Value));

                var first = bookmarks.IndexOf(endStart);
                var next = bookmarks.IndexOf(siblingStart);

                if (first >= next)
                {
                    continue;
                }

                sibling.Remove();
                end.InsertBeforeSelf(sibling);
                wasChange = true;
            }

            return wasChange;
        }
        protected virtual void FixTheBookmarkEndPosition(Body body, IEnumerable<BookmarkEnd> ends)
        {
            foreach (var end in ends)
            {
                var endBookmark = end;
                var start = body.Descendants<BookmarkStart>()
                                .Where(b => b.Id.Value.Equals(endBookmark.Id.Value))
                                .FirstOrDefault();

                var parent = end.Parent;
                if (start.IsNull())
                {
                    end.Remove();
                    continue;
                }

                if (parent.Equals(start.Parent))
                {
                    continue; // both in the same element
                }

                if (parent.Parent.Equals(start.Parent.Parent))
                {
                    // the end have fallen into next element
                    var moveAfter = end.ElementsBefore().Any(e => e is Run);
                    end.Remove();
                    if (moveAfter)
                    {
                        parent.InsertAfterSelf(end); // word bug, must be fixed
                    }
                    else
                    {
                        parent.InsertBeforeSelf(end); // word bug, must be fixed
                    }
                    parent = end.Parent;
                }

                if (!parent.Equals(start.Parent.Parent))
                {
                    continue;
                }
                if (start.ElementsBefore().Any(e => e is Run))
                {
                    end.Remove();
                    start.Parent.AppendChild(end); // the last inline was moved out of the element
                }
                // else end is at start.parent level, and will be processed as block
            }
        }
        /// <summary>
        /// Convert bookmarks into DocPart elements
        /// </summary>
        /// <param name="root"></param>
        /// <param name="bookmarks"></param>
        /// <param name="parts"></param>
        protected virtual void ProcessBookmarks(OpenXmlElement root,
            IEnumerable<BookmarkStart> bookmarks,
            IList<DocPart> parts)
        {
            foreach (var bookmark in bookmarks)
            {
                var start = bookmark;
                var end = root.Descendants<BookmarkEnd>().Where(b => b.Id.Value.Equals(start.Id.Value)).Single();

                if (bookmark.Parent.Equals(end.Parent))
                {
                    ProcessInline(bookmark, end, parts);
                }
                else
                {
                    ProcessBlock(bookmark, end, parts);
                }
            }
        }

        /// <summary>
        /// Bookmark has 'start' and 'end' inside the same Parent
        /// </summary>
        /// <param name="bookmark"></param>
        /// <param name="end"></param>
        /// <param name="parts"></param>
        protected virtual void ProcessInline(BookmarkStart bookmark, BookmarkEnd end, IList<DocPart> parts)
        {
            var docPart = parts.CreateDocPart(bookmark.Name.Value);
            var sib = bookmark.NextSibling();

            bookmark.InsertBeforeSelf(docPart);
            bookmark.Remove();

            while (!end.Equals(sib))
            {
                var old = sib;
                sib = old.NextSibling();
                old.Remove();
                docPart.Append(old);
            }

            end.Remove();
        }

        /// <summary>
        /// Bookmark starts in different Parent then ends.
        /// </summary>
        /// <param name="bookmark"></param>
        /// <param name="end"></param>
        /// <param name="parts"></param>
        protected virtual void ProcessBlock(BookmarkStart bookmark,
            BookmarkEnd end,
            IList<DocPart> parts)
        {
            var parent = bookmark.Parent;

            var docPart = parts.CreateDocPart(bookmark.Name.Value);
            var sib = parent.NextSibling();

            parent.InsertBeforeSelf(docPart);
            parent.Remove();
            bookmark.Remove();

            docPart.Append(parent);

            while (sib.Is()
                   && !sib.Equals(end)
                   && !sib.Equals(end.Parent))
            {
                var old = sib;
                sib = old.NextSibling();

                old.Remove();
                docPart.Append(old);
            }

            end.Remove();
        }
    }
}
